// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' as ui;
import 'package:ui/ui_web/src/ui_web.dart' as ui_web;

import 'common.dart';

void main() {
  internalBootstrapBrowserTest(() => testMain);
}

void testMain() {
  group('CanvasKit text', () {
    setUpCanvasKitTest();

    test("doesn't crash when using shadows", () {
      final ui.TextStyle textStyleWithShadows = ui.TextStyle(
        fontSize: 16,
        shadows: <ui.Shadow>[
          const ui.Shadow(
            blurRadius: 3.0,
            offset: ui.Offset(3.0, 3.0),
          ),
          const ui.Shadow(
            blurRadius: 3.0,
            offset: ui.Offset(-3.0, 3.0),
          ),
          const ui.Shadow(
            blurRadius: 3.0,
            offset: ui.Offset(3.0, -3.0),
          ),
          const ui.Shadow(
            blurRadius: 3.0,
            offset: ui.Offset(-3.0, -3.0),
          ),
        ],
        fontFamily: 'Roboto',
      );

      for (int i = 0; i < 10; i++) {
        final ui.ParagraphBuilder builder =
            ui.ParagraphBuilder(ui.ParagraphStyle(fontSize: 16));
        builder.pushStyle(textStyleWithShadows);
        builder.addText('test');
        final ui.Paragraph paragraph = builder.build();
        expect(paragraph, isNotNull);
      }
    });

    // Regression test for https://github.com/flutter/flutter/issues/78550
    test('getBoxesForRange works for LTR text in an RTL paragraph', () {
      // Create builder for an RTL paragraph.
      final ui.ParagraphBuilder builder = ui.ParagraphBuilder(
          ui.ParagraphStyle(fontSize: 16, textDirection: ui.TextDirection.rtl));
      builder.addText('hello');
      final ui.Paragraph paragraph = builder.build();
      paragraph.layout(const ui.ParagraphConstraints(width: 100));
      expect(paragraph, isNotNull);
      final List<ui.TextBox> boxes = paragraph.getBoxesForRange(0, 1);
      expect(boxes, hasLength(1));
      // The direction for this span is LTR even though the paragraph is RTL
      // because the directionality of the 'h' is LTR.
      expect(boxes.single.direction, equals(ui.TextDirection.ltr));
    });

    test('Renders tab as space instead of tofu', () async {
      // CanvasKit renders a tofu if the font does not have a glyph for a
      // character. However, Flutter opts-in to a CanvasKit feature to render
      // tabs as a single space.
      // See: https://github.com/flutter/flutter/issues/79153
      Future<ui.Image> drawText(String text) {
        const ui.Rect bounds = ui.Rect.fromLTRB(0, 0, 100, 100);
        final CkPictureRecorder recorder = CkPictureRecorder();
        final CkCanvas canvas = recorder.beginRecording(bounds);
        final CkParagraph paragraph = makeSimpleText(text);

        canvas.drawParagraph(paragraph, ui.Offset.zero);
        final ui.Picture picture = recorder.endRecording();
        return picture.toImage(100, 100);
      }

      // The backspace character, \b, does not have a corresponding glyph and
      // is rendered as a tofu.
      final ui.Image tabImage = await drawText('>\t<');
      final ui.Image spaceImage = await drawText('> <');
      final ui.Image tofuImage = await drawText('>\b<');

      expect(await matchImage(tabImage, spaceImage), isTrue);
      expect(await matchImage(tabImage, tofuImage), isFalse);
    });

    group('test fonts in flutterTester environment', () {
      final bool resetValue = ui_web.debugEmulateFlutterTesterEnvironment;
      ui_web.debugEmulateFlutterTesterEnvironment = true;
      tearDownAll(() {
        ui_web.debugEmulateFlutterTesterEnvironment = resetValue;
      });
      const List<String> testFonts = <String>['FlutterTest', 'Ahem'];

      test('The default test font is used when a non-test fontFamily is specified', () {
        final String defaultTestFontFamily = testFonts.first;

        expect(CkTextStyle(fontFamily: 'BogusFontFamily').fontFamily, defaultTestFontFamily);
        expect(CkParagraphStyle(fontFamily: 'BogusFontFamily').getTextStyle().fontFamily, defaultTestFontFamily);
        expect(CkStrutStyle(fontFamily: 'BogusFontFamily'), CkStrutStyle(fontFamily: defaultTestFontFamily));
      });

      test('The default test font is used when fontFamily is unspecified', () {
        final String defaultTestFontFamily = testFonts.first;

        expect(CkTextStyle().fontFamily, defaultTestFontFamily);
        expect(CkParagraphStyle().getTextStyle().fontFamily, defaultTestFontFamily);
        expect(CkStrutStyle(), CkStrutStyle(fontFamily: defaultTestFontFamily));
      });

      test('Can specify test fontFamily to use', () {
        for (final String testFont in testFonts) {
          expect(CkTextStyle(fontFamily: testFont).fontFamily, testFont);
          expect(CkParagraphStyle(fontFamily: testFont).getTextStyle().fontFamily, testFont);
        }
      });
    });

    test('empty paragraph', () {
      const double fontSize = 10.0;
      final ui.Paragraph paragraph = ui.ParagraphBuilder(CkParagraphStyle(
        fontSize: fontSize,
      )).build();
      paragraph.layout(const ui.ParagraphConstraints(width: double.infinity));

      expect(paragraph.getLineMetricsAt(0), isNull);
      expect(paragraph.numberOfLines, 0);
      expect(paragraph.getLineNumberAt(0), isNull);
    });

    test('Basic line related metrics', () {
      const double fontSize = 10;
      final ui.ParagraphBuilder builder = ui.ParagraphBuilder(CkParagraphStyle(
        fontStyle: ui.FontStyle.normal,
        fontWeight: ui.FontWeight.normal,
        fontSize: fontSize,
        maxLines: 1,
        ellipsis: 'BBB',
      ))..addText('A' * 100);
      final ui.Paragraph paragraph = builder.build();
      paragraph.layout(const ui.ParagraphConstraints(width: 100.0));

      expect(paragraph.numberOfLines, 1);

      expect(paragraph.getLineMetricsAt(-1), isNull);
      expect(paragraph.getLineMetricsAt(0)?.lineNumber, 0);
      expect(paragraph.getLineMetricsAt(1), isNull);

      expect(paragraph.getLineNumberAt(-1), isNull);
      expect(paragraph.getLineNumberAt(0), 0);
      expect(paragraph.getLineNumberAt(6), 0);
      // The last 3 characters on the first line are ellipsized with BBB.
      expect(paragraph.getLineMetricsAt(7), isNull);
    });

    test('Basic glyph metrics', () {
      const double fontSize = 10;
      final ui.ParagraphBuilder builder = ui.ParagraphBuilder(CkParagraphStyle(
        fontStyle: ui.FontStyle.normal,
        fontWeight: ui.FontWeight.normal,
        fontFamily: 'FlutterTest',
        fontSize: fontSize,
        maxLines: 1,
        ellipsis: 'BBB',
      ))..addText('A' * 100);
      final ui.Paragraph paragraph = builder.build();
      paragraph.layout(const ui.ParagraphConstraints(width: 100.0));

      expect(paragraph.getGlyphInfoAt(-1), isNull);

      // The last 3 characters on the first line are ellipsized with BBB.
      expect(paragraph.getGlyphInfoAt(0)?.graphemeClusterCodeUnitRange, const ui.TextRange(start: 0, end: 1));
      expect(paragraph.getGlyphInfoAt(6)?.graphemeClusterCodeUnitRange, const ui.TextRange(start: 6, end: 7));
      expect(paragraph.getGlyphInfoAt(7), isNull);
      expect(paragraph.getGlyphInfoAt(200), isNull);
    });

    test('Basic glyph metrics - hit test', () {
      const double fontSize = 10.0;
      final ui.ParagraphBuilder builder = ui.ParagraphBuilder(CkParagraphStyle(
        fontSize: fontSize,
        fontFamily: 'FlutterTest',
      ))..addText('Test\nTest');
      final ui.Paragraph paragraph = builder.build();
      paragraph.layout(const ui.ParagraphConstraints(width: double.infinity));

      final ui.GlyphInfo? bottomRight = paragraph.getClosestGlyphInfoForOffset(const ui.Offset(99.0, 99.0));
      final ui.GlyphInfo? last = paragraph.getGlyphInfoAt(8);
      expect(bottomRight, equals(last));
      expect(bottomRight, isNot(paragraph.getGlyphInfoAt(0)));

      expect(bottomRight?.graphemeClusterLayoutBounds, const ui.Rect.fromLTWH(30, 10, 10, 10));
      expect(bottomRight?.graphemeClusterCodeUnitRange, const ui.TextRange(start: 8, end: 9));
      expect(bottomRight?.writingDirection, ui.TextDirection.ltr);
    });

    test('rounding hack disabled by default', () {
      expect(ui.ParagraphBuilder.shouldDisableRoundingHack, isTrue);

      const double fontSize = 1.25;
      const String text = '12345';
      assert((fontSize * text.length).truncate() != fontSize * text.length);
      final ui.ParagraphBuilder builder = ui.ParagraphBuilder(
        ui.ParagraphStyle(fontSize: fontSize, fontFamily: 'FlutterTest'),
      );
      builder.addText(text);
      final ui.Paragraph paragraph = builder.build()
        ..layout(const ui.ParagraphConstraints(width: text.length * fontSize));

      expect(paragraph.maxIntrinsicWidth, text.length * fontSize);
      switch (paragraph.computeLineMetrics()) {
        case [ui.LineMetrics(width: final double width)]:
          expect(width, text.length * fontSize);
        case final List<ui.LineMetrics> metrics:
          expect(metrics, hasLength(1));
      }
    });

    test('setDisableRoundinghHack to false works in tests', () {
      bool assertsEnabled = false;
      assert(() {
        assertsEnabled = true;
        return true;
      }());
      if (!assertsEnabled){
        return;
      }

      if (ui.ParagraphBuilder.shouldDisableRoundingHack) {
        ui.ParagraphBuilder.setDisableRoundingHack(false);
        addTearDown(() => ui.ParagraphBuilder.setDisableRoundingHack(true));
      }

      assert(!ui.ParagraphBuilder.shouldDisableRoundingHack);
      const double fontSize = 1.25;
      const String text = '12345';
      assert((fontSize * text.length).truncate() != fontSize * text.length);
      final ui.ParagraphBuilder builder = ui.ParagraphBuilder(ui.ParagraphStyle(fontSize: fontSize, fontFamily: 'FlutterTest'));
      builder.addText(text);
      final ui.Paragraph paragraph = builder.build()
        ..layout(const ui.ParagraphConstraints(width: text.length * fontSize));
      expect(paragraph.computeLineMetrics().length, greaterThan(1));
    });

    // TODO(hterkelsen): https://github.com/flutter/flutter/issues/71520
  }, skip: isSafari || isFirefox);
}
